/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using VSLangProj80;
using EnvDTE80;
using VSLangProj;
using EnvDTE;
using DaveSexton.DocProject.Engine;
using DaveSexton.DocProject.Configuration;
using Microsoft.Win32;
using System.IO;
using System.Configuration;
using System.Configuration.Provider;
using System.Diagnostics;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	/// <remarks>
	/// <para>
	/// This class is thread-safe.
	/// </para>
	/// <para>
	/// To prevent deadlocks, never call any methods or use any properties of this class from a thread other than the main UI thread during the 
	/// <see cref="Build" /> process.  The <see cref="SynchronizationContext" /> of the main UI thread can be accessed via the 
	/// <see cref="BuildContext.Sync" /> property in most methods during the build process.  Use the returned object to marshal calls to the UI
	/// thread.
	/// </para>
	/// </remarks>
	public static class BuildController
	{
		#region Public Properties
		public static IBuildEngine ExecutingEngine
		{
			get
			{
				return executingEngine;
			}
		}
		#endregion

		#region Private / Protected
		private static readonly BuildEngineCollection engines = new BuildEngineCollection();
		private static IBuildEngine executingEngine;
		#endregion

		#region Methods
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool BuildAllDocProjects()
		{
			bool success = true;

			foreach (IBuildEngine engine in engines)
			{
				executingEngine = engine;

				if (!engine.Build())
					success = false;

				executingEngine = null;
			}

			return success;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool Build(IDocProject project)
		{
			return Build(project, BuildType.Default);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool Build(IDocProject project, BuildType buildType)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			executingEngine = GetEngineForProject(project);

			try
			{
				executingEngine.SetBuildTypeWithPrecedence(buildType);

				return executingEngine.Build();
			}
			finally
			{
				executingEngine = null;
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void SyncProjectItems(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			IBuildEngine engine = GetEngineForProject(project);

			engine.IncludeBuildItems();
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool Clean(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			executingEngine = GetEngineForProject(project);

			try
			{
				return executingEngine.Clean();
			}
			finally
			{
				executingEngine = null;
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool NotifyNameChanged(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (engines.Contains(project))
			{
				engines[project].Settings.Refresh();
				return true;
			}
			else
				return false;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool HasEngineForProject(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			return engines.Contains(project);
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IBuildEngine AddEngineForProject(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (engines.Contains(project))
				throw new ArgumentException(Errors.DocProjectExists, "project");

			Debug.WriteLine("Adding DocProject engine for: " + project.Name, "BuildController");

			IBuildEngine engine = project.Provider.CreateBuildEngine(project);

			engines.Add(engine);

			Debug.WriteLine("DocProject engine added for: " + project.Name, "BuildController");

			return engine;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static bool RemoveEngineForProject(IDocProject project)
		{
			if (project == null)
				throw new ArgumentNullException("project");

			if (engines.Contains(project))
			{
				Debug.WriteLine("Removing DocProject engine for: " + project.Name, "BuildController");

				IBuildEngine engine = engines[project];

				try
				{
					engines.Remove(engine);
				}
				finally
				{
					engine.Dispose();
				}

				Debug.WriteLine("DocProject engine removed for: " + project.Name, "BuildController");

				return true;
			}
			else
				return false;
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void ClearEngines()
		{
			// NOTE: The engine's project will not be available if the solution has closed; therefore, there's no need
			// to write to the debug window. (i.e., this method is called when a solution closes, so don't be tempted 
			// to call RemoveEngineForProject from here).
			while (engines.Count > 0)
			{
				engines[0].Dispose();
				engines.RemoveAt(0);
			}
		}

		/// <summary>
		/// Returns or creates the instance of an <see cref="IBuildEngine" /> implementation that performs builds for the specified 
		/// <paramref name="docProject" />.
		/// </summary>
		/// <param name="docProject">DocProject for which the build engine will be returned.</param>
		/// <returns><see cref="IBuildEngine" /> implementation that performss builds for the specified <paramref name="docProject" />.</returns>
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static IBuildEngine GetEngineForProject(IDocProject docProject)
		{
			if (docProject == null)
				throw new ArgumentNullException("docProject");

			if (!engines.Contains(docProject))
				return AddEngineForProject(docProject);
			else
				return engines[docProject];
		}		

		/// <summary>
		/// Calls <see cref="UpdateBuildEngines" /> and registers any DocProjects in the current solution that have not been registered. 
		/// </summary>
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void Refresh()
		{
			UpdateBuildEngines();

			foreach (VSAnyProject project in DocProjectEnvironment.FindProjects(null))
			{
				IDocProject docProject = project as IDocProject;

				if (docProject != null && !engines.Contains(docProject))
					AddEngineForProject(docProject);
			}
		}

		/// <summary>
		/// Refreshes all of the registered build engines.  This method will replace an existing engine with an appropriate engine for a given 
		/// DocProject if the configured provider has changed since the last time this method was called. 
		/// </summary>
		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void UpdateBuildEngines()
		{
			foreach (IBuildEngine engine in new BuildEngineCollection(engines))
			{
				IDocProject project = engine.Project;

				BuildEngineProvider provider = project.Provider;

				if (!provider.CreatesBuildEngineOfType(engine.GetType()))
				{
					engines.Remove(engine);

					engines.Add(provider.CreateBuildEngine(project));

					engine.Dispose();
				}
				else
					engine.Refresh();
			}
		}

		[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
		public static void Cancel()
		{
			if (executingEngine != null)
				executingEngine.Cancel();
		}
		#endregion
	}
}
